<?php
/*
 * Created  : Mon 16 Nov 2009 07:48:57 PM EST
 * Modified : Thu 19 Nov 2009 01:09:16 AM EST
 *
 * Copyright 2009 Gautam Iyer <gi1242@nospam.com> (Replace nospam with gmail).
 *
 * This file is part of phpss.
 *
 * phpss is free software: you can redistribute it and/or modify it under the 
 * terms of the GNU General Public License as published by the Free Software 
 * Foundation, either version 3 of the License, or (at your option) any later 
 * version.
 *
 * phpss is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
 * details.
 *
 * You should have received a copy of the GNU General Public License along with 
 * phpss.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

define( "RIGHT_TOP", 6 ); // Rotate -90 degrees
define( "LEFT_BOT", 8 ); // Rotate 90 degrees

/*
 * Resize all files in the array $files. $image_sizes should be like
 * $image_sizes = array(
 *	array(	"dir"	    => "subdir of $output_dir",
 *       	"width"     => 640,
 *		"height"    => 480, // Only one of width / height is required. 
 *				    // If both are provided, image will be 
 *				    // resized preserving aspect ratio to have 
 *				    // dimensions less than both provided. 
 *
 *		"quality"   => 75   // Optional
 *	      ),
 *
 *     array( "dir" => "different-dir", "width" => different-width, etc. )
 *   );
 */
function resize_images( $files, $image_sizes )
{
  global $nthreads;

  fwrite( STDERR, "Resizing images ($nthreads threads): " );

  $threadnum = 0;
  $child_pids = array();
  for( $i=1; $i < $nthreads; $i++ )
  {
    $pid = pcntl_fork();
    switch( $pid )
    {
      case -1:
	die( "Could not fork\n" );
	/* Not reached */

      case 0:
	/* Child. Store thread number. */
	$threadnum = $i;
	usleep( $threadnum * 100000 ); // Delay .1 sec to avoid mkdir errors

	break 2; // Break out of for loop so we don't creat more childs

      default:
	/* Parent. Store PID */
	$child_pids[] = $pid;
    }
  }
  

  /*
   * Now have $nthreads threads, each having their $threadnum set. Use each
   * thread to resize every $nthreads^th image, starting from $threadnum.
   */
  $nfiles = count( $files );
  for( $i=$threadnum; $i < $nfiles; $i += $nthreads )
  {
    /*
     * Loop over files first, since the kernel might cache the images so we 
     * won't have to read the same file from the hard disk more than once.
     */
    foreach( $image_sizes as $resize )
      resize_rotate( $resize, $files[$i], $threadnum );
  }

  // Kill all but the parent thread.
  if( $threadnum > 0 )
    exit(0);

  // Wait for all children to finish.
  foreach( $child_pids as $pid )
    pcntl_waitpid( $pid, $status  );
  fwrite( STDERR, RE . "done resizing.\n" );
}

/*
 * Resize and rotate a file to given dimensions.
 */
function resize_rotate( $resize, $f, $threadnum )
{
  global $output_dir;

  $thread_prefix = isset( $threadnum ) ? "$threadnum:" : "";

  try
  {
    if( !array_key_exists( 'dir', $resize ) )
      throw new Exception( "$f(No dir)" );

    $outfile = "$output_dir/$resize[dir]/" . basename( $f );
    if(
	file_exists( $outfile ) &&
	filemtime( $outfile ) > filemtime( $f )
      )
    {
      fwrite( STDERR, BD . "$thread_prefix$outfile" . RE . " " );
      return;
    }
    
    if(
	! is_dir( dirname( $outfile ) ) &&
	! mkdir( dirname( $outfile ), 0777, true )
      )
      throw new Exception( "$outfile(mkdir)" );

    // Read image file.
    $src = imagecreatefromjpeg( $f );
    if( !$src ) throw new Exception("$f(Can't open)");
    
    $exifinfo = exif_read_data( $f );
    if(
	$exifinfo && array_key_exists( 'Orientation', $exifinfo ) &&
	(
	  $exifinfo['Orientation'] == RIGHT_TOP ||
	  $exifinfo['Orientation'] == LEFT_BOT
	)
      )
    {
      // Image needs rotation.
      $needs_rotation = true;
    }
    else
      $needs_rotation = false;

    $width = imagesx( $src );
    $height = imagesy( $src );
    if( $width <= 0 || $height <= 0 )
      throw new Exception( "$f(Bad size)" );
    if( $needs_rotation )
      swap( $width, $height );

    if(
	array_key_exists( 'width', $resize ) &&
	array_key_exists( 'height', $resize )
      )
    {
      // Choose new_width/new_height preserving aspect ratio.
      if( $resize['width'] / $width * $height < $resize['height'] )
      {
	$new_width = $resize['width'];
	$new_height = round( $new_width / $width * $height );
      }
      else
      {
	$new_height = $resize['height'];
	$new_width = round( $new_height / $height * $width );
      }
    }

    elseif( array_key_exists( 'width', $resize ) )
    {
      $new_width = $resize['width'];
      $new_height = round( $new_width / $width * $height );
    }
    elseif( array_key_exists( 'height', $resize ) )
    {
      $new_height = $resize['height'];
      $new_width = round( $new_height / $height * $width );
    }
    else throw new Exception( "$f(No dims)" );

    if( $needs_rotation )
    {
      // Set all dimensions back to unrotated versiosn.
      swap( $new_height, $new_width );
      swap( $width, $height );
    }

    $quality = array_key_exists( 'quality', $resize ) ?
      $resize['quality'] : 75;

    $dst = imagecreatetruecolor( $new_width, $new_height );
    if(
	!$dst ||
	!imagecopyresampled( $dst, $src, 0, 0, 0, 0,
	    $new_width, $new_height, $width, $height )
      )
      throw new Exception( "$f(Can't resize)" );

    // Now rotate if necessary. It's much less work to rotate a small image, 
    // especailly if we rotate by hand, without using the GD library function.
    if( $needs_rotation )
    {
      if( function_exists( 'imagerotate' ) )
      {
	$dst = imagerotate( $dst,
	  $exifinfo['Orientation'] == RIGHT_TOP ? -90 : 90, 0 );
      }
      else
      {
	$dst = rotate90( $dst,
	  $exifinfo['Orientation'] == RIGHT_TOP ? -90 : 90, 0 );
      }
      if( !$dst ) throw new Exception( "$f(Can't rotate)" );
    }

    if( !imagejpeg( $dst, $outfile, $quality ) )
      throw new Exception( "$outfile(Can't write)" );

    // Success
    fwrite( STDERR, UL . $thread_prefix . $outfile . RE . " " );
  }
  catch( Exception $e )
  {
      fwrite( STDERR, ER . $thread_prefix . $e->getMessage() . RE . " " );
  }
}

/*
 * On debian systems imagerotate doesn't exist, since it is not complied with 
 * PHP's version of GD. So have to rotate by hand below.
 */
function rotate90( $img, $angle )
{
  assert( $angle == 90 || $angle == -90 );

  $width = imagesx($img);
  $height = imagesy($img);

  $newimg = imagecreatetruecolor( $height , $width );
  if( !$newimg )
    throw new Exception( "Can't create rotated" );

  for( $x = 0; $x < $width; $x++ )
  {
    for( $y=0; $y < $height; $y++ )
    {
      switch( $angle )
      {
	case -90:
	  imagecopy( $newimg, $img, ($height - 1) - $y, $x, $x, $y, 1, 1 );
	  break;

	case 90:
          imagecopy( $newimg, $img, $y, ($width - 1) - $x, $x, $y, 1, 1 );
	  break;

	default:
	  /* Not reached */
      }
    }
  }

  return $newimg;
}

function swap( &$a, &$b )
{
  $tmp = $a;
  $a = $b;
  $b = $tmp;
}
?>
